#pragma once
#include <memory>
#include <unordered_map>
#include <sstream>
#include <fstream>
#include <functional>
#include "Common.hpp"
#include "TcpServer.hpp"
#include "Util.hpp"
#include "Log.hpp"
#include "Sesssion.hpp"

const std::string space = " ";
const std::string line_break = "\r\n";
const std::string headers_sep = ": ";

using namespace myLog;


const std::string webroot = "./wwwroot";
const std::string homepage = "index.html";
const std::string page_404 = "404.html";



//这里要说明的是：Http协议，是不依赖于第三方库进行序列化和反序列化的！

//2025——9——5：
//今天这里的代码加一些功能：
//1.测试Cookie
//2.使用Session & Cookie进行管理
//上述操作都是基于登录页面处理的！

//Http请求格式
class HttpRequest{
public:
    HttpRequest()
        :_blank_line(line_break),
        _is_interact(false),
        _args("")
    {}
    ~HttpRequest(){}


//其实，今天来说，客户端是浏览器！我们可以直接拿浏览器来访问我们写的服务器。所以，请求的序列化写不写都可以！
//但是就不写了，就留一个方法
//因为http协议已经有固定的序列化方式和反序列化格式了！
    std::string Serialize(){return "";}

    void ParseRequestLine(std::string& request_line){
        std::stringstream ss(request_line);
        //以空格作为分隔符，将字符串分割后依次插入对应的字段
        ss >> _req_method >> _uri >> _http_version;
    }

    bool GetKV_AndSet(std::string headline){
        //key: value
        size_t pos = headline.find(headers_sep);
        if(pos == std::string::npos) return false;
        std::string key = headline.substr(0, pos);
        std::string value = headline.substr(pos + headers_sep.size());
        _headers[key] = value;
        return true;
    }

//反序列化还是要写的
    bool DeSerialize(std::string& req_str){
        //1.提取请求行
        std::string request_line;
        Util::ReadOneLine(req_str, &request_line, line_break);
        LOG(LogLevel::DEBUG) << "request_line: " << request_line;
        
        //2.把请求行放入到对应的字段 -> 使用stringstream
        ParseRequestLine(request_line);

        //这里要注意，uri对应的就是要访问的服务器上对应的资源
        if(_uri == "/") _uri = webroot + _uri + homepage;
        else _uri = webroot + _uri; 

        LOG(LogLevel::DEBUG) << "req_method: " << _req_method;
        LOG(LogLevel::DEBUG) << "uri: " << _uri;
        LOG(LogLevel::DEBUG) << "http_version: " << _http_version;

        if(_req_method == "POST" || _req_method == "post") _is_interact = true;
        
        //3.把报头提取出进行分析
        std::string header_line;
        Util::ReadOneLine(req_str, &header_line, line_break);
        //到这里都正常


        //只要不是读出来 ""，就表明还是报头  
        while(header_line != ""){
            //此时读到了一行 -> key: value(正常来说，如果是空行读出来就是 "")
            GetKV_AndSet(header_line);

            header_line.clear();
            Util::ReadOneLine(req_str, &header_line, line_break);
        }

        //此时读到了空行就退出循环了，并且ReadOneLine中已经把读到的给删除了
        DebugHeaders();

        //如果使用POST来进行传参，那么参数在正文，这里就不管了
        //req_str剩下的就是正文了！
        _text = req_str;
        LOG(LogLevel::DEBUG) << _text;

        //如果使用GET方法传内容给服务器，那么参数在uri上，所以得对uri作进一步解析
        std::string tmp = "?";
        auto pos = _uri.find(tmp);
        if(pos != std::string::npos) {
            // /login?username=adasdad&password=adaadsasd
            _args = _uri.substr(pos + tmp.size());//截取参数
            _uri = _uri.substr(0, pos);//获取真正的服务！
            _is_interact = true;
        }

        LOG(LogLevel::DEBUG) << "req_method: " << _req_method;
        LOG(LogLevel::DEBUG) << "uri: " << _uri;
        LOG(LogLevel::DEBUG) << "http_version: " << _http_version;
        if(_args != "") LOG(LogLevel::DEBUG) << "uri args: " << _args;
        LOG(LogLevel::DEBUG) << "text: " << _text;

        return true;
    }

    void DebugHeaders(){
        for(auto& head : _headers){
            LOG(LogLevel::DEBUG) << head.first << headers_sep << head.second;
        }
    }

    std::string GetUri(){return _uri;}

    std::string GetText(){return _text;}

    bool Is_Interact(){return _is_interact;}

    std::string GetArgs(){return _args;}

    std::string GetHeaders(const std::string& key){
        if(_headers.find(key) == _headers.end()) return "";
        else return _headers[key];
    }

private:
    std::string _req_method;
    std::string _uri;
    std::string _http_version;
    std::unordered_map<std::string, std::string> _headers;
    std::string _blank_line;
    std::string _text;

    bool _is_interact;//判断是否有交互 -> 后序来实现交互功能(如登录请求...)   /login   /register
    std::string _args;//参数(如果使用GET方法，参数会被设置到_uri上)
};

//Http应答格式
class HttpResponse{
public:
    HttpResponse()
        :_blank_line(line_break),
        _http_version("/HTTP/1.0")
    {}
    ~HttpResponse(){}



    std::string Serialize(){
        std::string status_line = _http_version + space + 
        std::to_string(_status_code) + space + _code_description + line_break;

        std::string head_line;
        for(auto& head : _headers){
            std::string oneline = head.first + headers_sep + head.second + line_break;
            head_line += oneline;
        }
        return status_line + head_line + _blank_line + _text;
    }

    //今天来讲，反序列化Response是客户端做 -> 浏览器做，我们不需要写
    bool DeSerialize(){return true;}

    void SetTargetFile(const std::string target_file){
        _target_file = target_file;
    }

    void SetCodeAndDesc(int code){
        _status_code = code;
        switch(code){
            case 404:
                _code_description = "Not Found";
                break;
            case 200:
                _code_description = "OK";
                break;
            case 301:
                _code_description = "Moved Permanently";
                break;
            case 302:
                _code_description = "See Other";
                break;
            default:
                break;
        }
    }

    std::string UriToSuffix(const std::string uri){
        auto pos = uri.rfind(".");
        if(pos == std::string::npos) return "text/html";
        std::string suffix = uri.substr(pos);
        MimeTypes mime;
        return mime.getMimeType(suffix);
    }

    void SetHeaders(const std::string& key, const std::string& value){
        if(_headers.find(key) != _headers.end()) return;
        _headers.emplace(key, value);
    }


    bool MakeResponse(){
        bool res = Util::ReadFileContent(_target_file, &_text);
        int text_size = 0;//正文长度，后序设置长度Content-Length
        if(!res){
#define TWO
#ifdef  TWO
            //这里可以尝试试用一下重定向的方式，设置状态码301 / 302
            SetCodeAndDesc(301);  //永久重定向
            //SetCodeAndDesc(302);    //短暂重定向
            SetHeaders("Location", "/404.html");
#endif

//#define ONE
#ifdef ONE
            LOG(LogLevel::DEBUG) << "client want get" << _target_file << "but not found";
            _text = "";
            SetCodeAndDesc(404);
            _target_file = webroot + "/" + page_404;

            text_size = Util::GetFileSize(_target_file);
            Util::ReadFileContent(_target_file, &_text);
            //此时读到了内容（404page）就放到正文内了！
            
            //然后需要设置一些字段进入到报头中（这里就先只设置两个）
            std::string content_type = UriToSuffix(_target_file);
            SetHeaders("Content-Type", content_type);
            SetHeaders("Content-Length", std::to_string(text_size));    
#endif        
        }
        else{
            LOG(LogLevel::DEBUG) << "client read form: " << _target_file;
            SetCodeAndDesc(200);
            text_size = Util::GetFileSize(_target_file);
            std::string content_type = UriToSuffix(_target_file);
            SetHeaders("Content-Type", content_type);
            SetHeaders("Content-Length", std::to_string(text_size));
        }
        return true;
    }

    void SetText(std::string& text){_text = text;}

//为了方便服务端使用，这里的应答相关字段就用public修饰了。要不然进行修改的时候很麻烦！
public:
    std::string _http_version;
    int _status_code;
    std::string _code_description;
    std::unordered_map<std::string, std::string> _headers;
    std::string _blank_line;
    std::string _text;

    std::string _target_file;
    //要访问的资源 -> 以便后序方便输入正文！

    //有一些报头属性是多次出现的，如Set-Cookie，这里就加多一个这个使用！
    //内部的string就是一个个的key: value
    //std::vector<std::string> _multi_headers;
    //但是这里就不写了，知道怎么实现即可！
};


//为了用到会话管理，这里需要再加一个参数 -> 即会话管理者
using http_route_t = std::function<void(HttpRequest&, HttpResponse&, SessionManager&)>;

class Http{
public:
    Http(uint16_t port)
        :_server(std::make_unique<TcpServer>(port))
    {}

    ~Http(){}

    bool RegisterRoute(std::string func_name, http_route_t func){
        //如(/login, Login)
        func_name = webroot + func_name; //./wwwroot/login
        if(_route.find(func_name) == _route.end()){
            //该任务不存在于表中 -> 可以插入 //这里就规定，插入的名字就是传入func的对应的小写
            auto it = _route.emplace(func_name, func);
            return it.second;
        }
        return false;
    }

//这里就默认都是找的到的！
    bool AnalyseRequestLine(std::string& reqline, const std::string& key, std::string* value){
        //从请求行报文中，根据key读取对应的value
        size_t key_pos = reqline.find(key);//key_pos为key字符子串的起始位置
        if(key_pos == std::string::npos) return false;

        size_t value_pos = key_pos + key.size() + headers_sep.size();//value_pos为value字符子串的起始位置
        size_t value_end = reqline.find(line_break, value_pos);//从value_pos开始找"\r\n"
        if(value_end == std::string::npos) return false;

        *value = reqline.substr(value_pos, value_end - value_pos);
        return true;
    }


    bool ReadAllRequestHeader(std::string& inbuffer, std::string* text){
        //我们已经有了从一个大字符串中，切割字符串的能力了
        //这里的大报文都是以"\r\n"作为分割的，我们读取的时分隔符前面的
        //所以，我们可以一直读取，直到读到空行了，能得到完整的报头！
        //header内有一个属性：Content-length，其存储的时正文的长度！
        std::string oneline;
        //报文可能有若干情况：
        //四分之一条，半条、一整条，多条...
        //但是，今天是一个客户端对应一个进程 -> 发送来多条那就可能是客户端多次请求
        //这里一次只弄一条出来！

        while(Util::ReadOneLine(inbuffer, &oneline, line_break)){
            //只要为真，就说明还有"\r\n"可以读到，就还有机会出现空格
            *text += oneline + line_break;
            if(oneline == "") return true;
            oneline.clear();           
        }
        return false;
    }

//这里不写死循环了，只做短服务
    void HandleHttpRequest(std::shared_ptr<Socket> socket, const InetAddr& client){
        //大概率是能读到至少一个完整报文的！
        std::string readbuffer;
        int n = socket->Recv(&readbuffer);

        //但是，这里怎么能够保证报文的完整性呢？ -> 在Netcal那里实现过，这里模拟一下
        //这里只需要实现Decode即可，因为前面已经有一堆的字段了(已经具备Encode了)！(有效载荷在blank_line后面)
        if(n > 0){
            LOG(LogLevel::DEBUG) << client.GetFormatStr() << ": 发送来一个请求报文";
            //首先，得保证读到完整的请求->如果这一次没能成功读到完整请求，就不进行处理了！
            std::string all_reqline;
            if(ReadAllRequestHeader(readbuffer, &all_reqline) == false) return;
            //读到完整的请求报头 -> all_reqline
            //all_reqline里面有一个字段是指向正文长度的(前提是，Http请求中，正文部分长度 > 0，要不然其实是看不到的！)

            //如果发送来的正文长度 == 0，看不到这个字段！
            std::string text_len;
            if(AnalyseRequestLine(all_reqline, "Content-Length", &text_len) == false) text_len = "0";

            //成功读取长度到text_len -> 需要转成整数
            int len = std::stoi(text_len);

            //读取正文(从readbuffer中, 长度为len)
            if(readbuffer.size() < len) return; //正文长度不对！

            std::string text = readbuffer.substr(0, len);
            std::string req_str = all_reqline + text;

            //反序列化
            HttpRequest hreq;
            hreq.DeSerialize(req_str);

            //应答对应的协议结构
            HttpResponse hresp;

            //登陆界面的cookie测试，有的话直接进入登录！
            std::cout << hreq.GetUri() << std::endl;
            if(hreq.GetUri() == webroot + "/login.html"){
                std::string cookie_str = hreq.GetHeaders("Cookie");
                std::cout << cookie_str << std::endl;
                /* auto pos1 = cookie_str.find("username");
                auto pos2 = cookie_str.find("password");
                if(pos1 != std::string::npos && pos2 != std::string::npos){
                    //cookie中有对应的账户密码，直接跳转到登录页面！
                    hresp.SetCodeAndDesc(302);
                    //千万不要用301永久重定向，要不然浏览器就会直接认为，访问login.html就是再访问success_login.html了！
                    hresp.SetHeaders("Location", "/success_login.html");
                    socket->Send(hresp.Serialize());
                    return;
                } */

                auto pos = cookie_str.find("sessionid");
                //auto ppos = cookie_str.find("ecpires");
                if(pos != std::string::npos){ //&& ppos != std::string::npos){
                    //今天这里是没有把过期时间的cookie带进来，就是简单模拟一下cookie和session的实现！
                    //其实这里面还会存在着大量的管理session的方式，这里我就不写了!
                    hresp.SetCodeAndDesc(302);
                    hresp.SetHeaders("Location", "/success_login.html");
                    socket->Send(hresp.Serialize());
                    return;
                }
            }

//今天这里加多一步，反序列化后，就需要知道当前是否需要进行交互了
            if(hreq.Is_Interact()){
                //需要进行交互
                std::string service_name = hreq.GetUri();
                //但是，这个服务可能不存在于_route表中
                if(_route.find(service_name) == _route.end()){
                    //重定向到对应的404网页
                    hresp.SetCodeAndDesc(301);
                    hresp.SetHeaders("Location", "/404.html");
                    socket->Send(hresp.Serialize());                    
                }

                //对登陆服务采用简单地cookie测试，即cookie中要是带有账户密码，就直接进入登陆页面
                else{
                    _route[service_name](hreq, hresp, _session_manager);
                    std::string res_str = hresp.Serialize();
                    socket->Send(res_str);
                }
                return;
            }


            //如果不需要进行交互访问，只访问静态资源，就走原来的逻辑！

            //分析请求 + 制作应答
            //反序列化的时候，已经把要访问的web根目录底下的文件进行处理了！
            hresp.SetTargetFile(hreq.GetUri());
           

            hresp.MakeResponse();
            
            //应答进行序列化
            std::string resp_str = hresp.Serialize();

            //发送应答
            socket->Send(resp_str);
        }

        //用来测试是否能读到报文并反序列化
        /* HttpRequest req;
        req.DeSerialize(readbuffer); */
    }

    void HttpServerInit(){
        _server->Init();
    }

    void HttpServerStart(){
        _server->Run([this](std::shared_ptr<Socket> socket, const InetAddr& client){
            this->HandleHttpRequest(socket, client);
        });
    }

private:
    std::unique_ptr<TcpServer> _server;
    std::unordered_map<std::string, http_route_t> _route;
    SessionManager _session_manager;
};